# -*- coding: ascii -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
#
# gtk notes - gtk based note viewer
# Copyright (C) 2007  Chris Clark
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

### FIXME need exception trap, gtk just keeps on going! e.g. load a non utf8 data set (e.g. cp1252)
### TODO! compare gui encoding to system encoding for performance, may not need to decode/encode. (Similarly for file saving if file encoding is the same)
### TODO! Markdown sytles, call them bold, etc.
### TODO add new notes to tree
### TODO! delete notes from tree
### TODO! create directoryies (and add to tree)

import sys
import os
import locale
import re

import gtk

## http://code.google.com/p/pygtkcodebuffer/
from gtkcodebuffer import CodeBuffer, SyntaxLoader

## http://sourceforge.net/projects/pygtkforms/
from gtkform import create_gtk_dialog_from_object, create_gtk_dialog_from_dict


import pytombo.config
import pytombo.search
import pytombo.tombonote
import pytombo.notedown
import pytombo.chi_io ## FIXME should not really need to deal with this, allow tombonote to have same exceptions?


USE_GLADE=True
#
if USE_GLADE:
    # generated with glade2py from http://hocr.berlios.de
    from generated_gtktombo_glade import MainWindow as window1
    ## needs glade, so also needs glade file; gtktombo.glade
    
    ## TODO! Consider using:
    ##      http://faq.pygtk.org/index.py?req=show&file=faq22.004.htp
    ##      or other libs (like Kiwi)
else:
    # generated from glade2py.py from http://www.nicotine-plus.org/browser/trunk/nicotine+/pynicotine/gtkgui/glade2py.py?rev=427
    ## glc and gladepyc do not support glade files later than glade1
    ## TODO cnosider updating (e.g. support toolbars, named parameters, etc.)
    from generated_gtktombo import window1 ## pure python no glade


## from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/541082
def urepr(x):
    import re, unicodedata

    def toname(m):
        try:
            return r"\N{%s}" % unicodedata.name(unichr(int(m.group(1), 16)))
        except ValueError:
            return m.group(0)

    return re.sub(
        r"\\[xu]((?<=x)[0-9a-f]{2}|(?<=u)[0-9a-f]{4})",
        toname,
        repr(x)
    )

def myrename(src, dst):
    """(Non-attomic) file rename that will overwrite desination if it already exists.
    Under win32 this is not strictly safe as it is not atomic but acceptable
    if used under certain conditions (i.e. this module for note backup with
    history of maximum 1 version!)
    """
    if sys.platform == 'win32':
        # do horrible hack
        if os.path.exists(dst):
            os.remove(dst)
        
    os.rename(src, dst)

# TODO propagate mybackup() around, replacing rename calls...
def mybackup(filename, backup_filename=None, backup_extn='.bak'):
    if backup_filename is None:
            backup_filename = filename + backup_extn
    myrename(filename, backup_filename)
    return backup_filename

def save_text_note(filename, notetext, original_filename=None, backup_extn='.bak', windows_newlines=False, keep_backups=True):
    """Save plain text memo/note to filename.
    Takes a backup before saving in case of problems.
    Backup retained or deleted based on preference.
    
    ## rename to backup file
    ## then do save
    ## if save successful and backups are disabled; remove backup file
    """
    if original_filename is None:
        # _may_ have an existing memo to backup
        if os.path.exists(filename):
            backup_filename = filename + backup_extn
            myrename(filename, backup_filename)
    else: #if original_filename is not None:
        # definitely have existing memo that needs to be backed up
        backup_filename = original_filename + backup_extn
        myrename(original_filename, backup_filename)

    if isinstance(notetext, unicode):
        singlebyte_str = unicode_to_fileencoding(notetext)
    else:
        ## Assume byte string
        singlebyte_str = notetext
    ## pass into lower routine
    #if 'windows_newlines'
    #    ## should have done this to Unicode string not byte stream? E.g. what if encoding is UTF16
    #    singlebyte_str = unix2dos(singlebyte_str)
    ## TODO put save in try block? and restore backup on failure?
    ## we took the original backup just in case the save got completly screwed up
    pytombo.tombonote.saveplain_file(filename, singlebyte_str)###, dos_newlines=True)
    ## TODO - if not retain backup (and if original_filename is not None); if not keep_backups: os.remove(backup_filename)
    ## TODO encryption
    ## TomboNote.encrypt_file(filename, singlebyte_str, password)


#textIcon = gtk.gdk.pixbuf_new_from_file('textnote.xpm')

iconview = gtk.IconView()
## http://www.pygtk.org/pygtk2reference/gtk-stock-items.html
#textIcon = iconview.render_icon(gtk.STOCK_APPLY, gtk.ICON_SIZE_BUTTON)
#textIcon = iconview.render_icon(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
dirIcon = iconview.render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_BUTTON)
textIcon = iconview.render_icon(gtk.STOCK_EDIT, gtk.ICON_SIZE_BUTTON)
encryptedIcon = iconview.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_BUTTON)
#encryptedIcon = iconview.render_icon(gtk.STOCK_MEDIA_RECORD, gtk.ICON_SIZE_BUTTON)
#textIcon = gtk.image_new_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_MENU).get_pixbuf()

NOTE_ICON=0
NOTE_NAME=1
NOTE_DIR=2

GLOBALsystem_encoding=None
gui_encoding='utf8' ## GTK artifact
def add_notes_to_tree(the_tree, note_dirname, parent_node=None):
        # add notes to a GTK TreeStore (not the same as a TreeView!)
        # 
        # check out pytombo.search.recurse_notes()
        # check out pytombo.tombonote..remove_leading_path
        dir_list, file_list = pytombo.search.directory_contents(note_dirname)
        for dir_name in dir_list:
            if not isinstance(dir_name, unicode):
                # probably in local encoding. We should convert to Unicode, in case there is a non-ASCII character. 
                # e.g. latin1 under Windows, utf8 for most modern Linux distros
                dir_name = dir_name.decode(GLOBALsystem_encoding)
            
            parent = the_tree.append(parent_node, [dirIcon, '%s' % dir_name, '%s' % note_dirname])
            """
            ## either add a fake child (and only add real notes if Clicked) or just recurse through
            for child in range(1):
                the_tree.append(parent, ['FAKE CHILD'])
            """
            add_notes_to_tree(the_tree, os.path.join(note_dirname, dir_name), parent_node=parent)
            ## TODO! if parent still has no sub entries add a FAKE (hidden?) one so that it shows like a dir?
        for note_name in file_list:
            if not isinstance(note_name, unicode):
                # probably in local encoding. We should convert to Unicode, in case there is a non-ASCII character. 
                # e.g. latin1 under Windows, utf8 for most modern Linux distros
                note_name = note_name.decode(GLOBALsystem_encoding)
            
            temp_icon = textIcon
            if pytombo.search.is_encrypted(note_name):
                temp_icon = encryptedIcon
            ## todo cell writer/render text or encrypted bitmap/icon
            #parent = the_tree.append(parent_node, ['%s' % note_name])
            #parent = the_tree.append(parent_node, ['%s' % note_name, '%s' % note_dirname])
            parent = the_tree.append(parent_node, [temp_icon, '%s' % note_name, '%s' % note_dirname])


def highlightText(buffer, searchStr):
    ## from http://www.daa.com.au/pipermail/pygtk/2005-July/010663.html
    ## searchStr is case senstive..
    #buffer.create_tag("mysearch_hilight", foreground="red")
    mysearch_hilight = 'mysearch_hilight'
    taginfo = buffer.get_tag_table().lookup(mysearch_hilight)
    if taginfo is None:
        buffer.create_tag(mysearch_hilight, background="red")
    start, end = buffer.get_bounds()
    finished = False

    while finished == False:
        res = start.forward_search(searchStr, gtk.TEXT_SEARCH_TEXT_ONLY)
        if not res:
            finished = True
        else:
            matchStart, matchEnd = res
            buffer.apply_tag_by_name(mysearch_hilight, matchStart, matchEnd)
            #start = matchStart
            start = matchEnd

NOTE_TYPE_TEXT = 1
NOTE_TYPE_ENC = 2

# Taken from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473786
# also see http://www.wellho.net/mouth/296_Using-a-Python-dictionary-as-a-holder-of-object-attributes.html
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473790
# consider putting into a module
# using with Python 2.3
class AttrDict(dict):
    """A dictionary with attribute-style access. It maps attribute access to
    the real dictionary.  """
    def __init__(self, init={}):
        dict.__init__(self, init)

    def __getstate__(self):
        return self.__dict__.items()

    def __setstate__(self, items):
        for key, val in items:
            self.__dict__[key] = val

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, dict.__repr__(self))

    def __setitem__(self, key, value):
        return super(AttrDict, self).__setitem__(key, value)

    def __getitem__(self, name):
        return super(AttrDict, self).__getitem__(name)

    def __delitem__(self, name):
        return super(AttrDict, self).__delitem__(name)

    __getattr__ = __getitem__
    __setattr__ = __setitem__

    def copy(self):
        ch = AttrDict(self)
        return ch

#############
def _(s):
    ## Fake gettext
    return s

### Consider using Kiwi for dialogs? http://www.async.com.br/projects/kiwi/getit.html is it Maemo ready?
## FIXME TODO Win32 only, password dialog is supposed to be modal, can "loose" prompt window, needs to take focus and retain it!
#From kiwi http://www.async.com.br/projects/kiwi/getit.html
import atk
_IMAGE_TYPES = {
    gtk.MESSAGE_INFO: gtk.STOCK_DIALOG_INFO,
    gtk.MESSAGE_WARNING : gtk.STOCK_DIALOG_WARNING,
    gtk.MESSAGE_QUESTION : gtk.STOCK_DIALOG_QUESTION,
    gtk.MESSAGE_ERROR : gtk.STOCK_DIALOG_ERROR,
}
_BUTTON_TYPES = {
    gtk.BUTTONS_NONE: (),
    gtk.BUTTONS_OK: (gtk.STOCK_OK, gtk.RESPONSE_OK,),
    gtk.BUTTONS_CLOSE: (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,),
    gtk.BUTTONS_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,),
    gtk.BUTTONS_YES_NO: (gtk.STOCK_NO, gtk.RESPONSE_NO,
                         gtk.STOCK_YES, gtk.RESPONSE_YES),
    gtk.BUTTONS_OK_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                            gtk.STOCK_OK, gtk.RESPONSE_OK)
}
class HIGAlertDialog(gtk.Dialog):
    def __init__(self, parent, flags,
                 type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_NONE):
        if not type in _IMAGE_TYPES:
            raise TypeError(
                "type must be one of: %s", ', '.join(_IMAGE_TYPES.keys()))
        if not buttons in _BUTTON_TYPES:
            raise TypeError(
                "buttons be one of: %s", ', '.join(_BUTTON_TYPES.keys()))

        gtk.Dialog.__init__(self, '', parent, flags)
        self.set_border_width(5)
        self.set_resizable(False)
        self.set_has_separator(False)
        # Some window managers (ION) displays a default title (???) if
        # the specified one is empty, workaround this by setting it
        # to a single space instead
        self.set_title(" ")
        self.set_skip_taskbar_hint(True)
        self.vbox.set_spacing(14)

        # It seems like get_accessible is not available on windows, go figure
        if hasattr(self, 'get_accessible'):
            self.get_accessible().set_role(atk.ROLE_ALERT)

        self._primary_label = gtk.Label()
        self._secondary_label = gtk.Label()
        self._details_label = gtk.Label()
        self._image = gtk.image_new_from_stock(_IMAGE_TYPES[type],
                                               gtk.ICON_SIZE_DIALOG)
        self._image.set_alignment(0.5, 0.0)

        self._primary_label.set_use_markup(True)
        for label in (self._primary_label, self._secondary_label,
                      self._details_label):
            label.set_line_wrap(True)
            label.set_selectable(True)
            label.set_alignment(0.0, 0.5)

        hbox = gtk.HBox(False, 12)
        hbox.set_border_width(5)
        hbox.pack_start(self._image, False, False)

        vbox = gtk.VBox(False, 0)
        hbox.pack_start(vbox, False, False)
        vbox.pack_start(self._primary_label, False, False)
        vbox.pack_start(self._secondary_label, False, False)

        self._expander = gtk.expander_new_with_mnemonic(
            _("Show more _details"))
        self._expander.set_spacing(6)
        self._expander.add(self._details_label)
        vbox.pack_start(self._expander, False, False)
        self.vbox.pack_start(hbox, False, False)
        hbox.show_all()
        self._expander.hide()
        self.add_buttons(*_BUTTON_TYPES[buttons])
        self.label_vbox = vbox

    def set_primary(self, text):
        self._primary_label.set_markup(
            "<span weight=\"bold\" size=\"larger\">%s</span>" % text)

    def set_secondary(self, text):
        self._secondary_label.set_markup(text)

    def set_details(self, text):
        self._details_label.set_text(text)
        self._expander.show()

    def set_details_widget(self, widget):
        self._expander.remove(self._details_label)
        self._expander.add(widget)
        widget.show()
        self._expander.show()

def password(primary='', secondary='', parent=None, confirm=False):
    """
    Shows a password dialog and returns the password entered in the dialog
    @param primary: primary text
    @param secondary: secondary text
    @param parent: a gtk.Window subclass or None
    @param confirm: whether to ask for the password twice (and validate)
    @returns: the password or None if none specified
    @rtype: string or None
    """
    if not primary:
        raise ValueError("primary cannot be empty")

    d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL,
                       type=gtk.MESSAGE_QUESTION,
                       buttons=gtk.BUTTONS_OK_CANCEL)
    d.set_default_response(gtk.RESPONSE_OK)

    d.set_primary(primary + '\n')
    if secondary:
        secondary += '\n'
        d.set_secondary(secondary)

    hbox = gtk.HBox()
    hbox.set_border_width(6)
    hbox.show()
    d.label_vbox.pack_start(hbox)

    label = gtk.Label(_('Password:'))
    label.show()
    hbox.pack_start(label, False, False)

    entry = gtk.Entry()
    entry.set_invisible_char(u'\u2022')
    entry.set_visibility(False)
    entry.show()

    d.add_action_widget(entry, gtk.RESPONSE_OK)
    # FIXME: Is there another way of connecting widget::activate to a response?
    d.action_area.remove(entry)
    hbox.pack_start(entry, True, True, 12)
    
    if confirm:
        hbox2 = gtk.HBox()
        hbox2.set_border_width(6)
        hbox2.show()
        d.label_vbox.pack_start(hbox2)

        label2 = gtk.Label(_('Confirm Password:'))
        label2.show()
        hbox2.pack_start(label2, False, False)

        entry2 = gtk.Entry()
        entry2.set_invisible_char(u'\u2022')
        entry2.set_visibility(False)
        entry2.show()

        d.add_action_widget(entry2, gtk.RESPONSE_OK)
        # FIXME: Is there another way of connecting widget::activate to a response?
        d.action_area.remove(entry2)
        hbox2.pack_start(entry2, True, True, 12)
    
    response = gtk.RESPONSE_OK ## rather a hack :-0
    while response == gtk.RESPONSE_OK:
        response = d.run()
        if response == gtk.RESPONSE_OK:
            password = entry.get_text()
            if not confirm:
                break
            confirm_password = entry2.get_text()
            if confirm_password != password:
                dummy_result = error(_('Passwords do not match.'))
            else:
                break

    if response != gtk.RESPONSE_OK:
        password = None
    d.destroy()
    return password

def messagedialog(dialog_type, short, long=None, parent=None,
                  buttons=gtk.BUTTONS_OK, default=-1):
    """Create and show a MessageDialog.

    @param dialog_type: one of constants
      - gtk.MESSAGE_INFO
      - gtk.MESSAGE_WARNING
      - gtk.MESSAGE_QUESTION
      - gtk.MESSAGE_ERROR
    @param short:       A header text to be inserted in the dialog.
    @param long:        A long description of message.
    @param parent:      The parent widget of this dialog
    @type parent:       a gtk.Window subclass
    @param buttons:     The button type that the dialog will be display,
      one of the constants:
       - gtk.BUTTONS_NONE
       - gtk.BUTTONS_OK
       - gtk.BUTTONS_CLOSE
       - gtk.BUTTONS_CANCEL
       - gtk.BUTTONS_YES_NO
       - gtk.BUTTONS_OK_CANCEL
      or a tuple or 2-sized tuples representing label and response. If label
      is a stock-id a stock icon will be displayed.
    @param default: optional default response id
    """
    if buttons in (gtk.BUTTONS_NONE, gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE,
                   gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO,
                   gtk.BUTTONS_OK_CANCEL):
        dialog_buttons = buttons
        buttons = []
    else:
        if buttons is not None and type(buttons) != tuple:
            raise TypeError(
                "buttons must be a GtkButtonsTypes constant or a tuple")
        dialog_buttons = gtk.BUTTONS_NONE

    if parent and not isinstance(parent, gtk.Window):
        raise TypeError("parent must be a gtk.Window subclass")

    d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL,
                       type=dialog_type, buttons=dialog_buttons)
    if buttons:
        for text, response in buttons:
            d.add_buttons(text, response)

    d.set_primary(short)

    if long:
        if isinstance(long, gtk.Widget):
            d.set_details_widget(long)
        elif isinstance(long, basestring):
            d.set_details(long)
        else:
            raise TypeError(
                "long must be a gtk.Widget or a string, not %r" % long)

    if default != -1:
        d.set_default_response(default)

    if parent:
        d.set_transient_for(parent)
        d.set_modal(True)

    response = d.run()
    d.destroy()
    return response

def _simple(type, short, long=None, parent=None, buttons=gtk.BUTTONS_OK,
          default=-1):
    if buttons == gtk.BUTTONS_OK:
        default = gtk.RESPONSE_OK
    return messagedialog(type, short, long,
                         parent=parent, buttons=buttons,
                         default=default)

def error(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1):
    ## FIXME TODO Win32 only
    ### Windows (XP) if the error windows
    ### is lost underneath other windows it is possible to bring main GTK window to front
    ### and not _see_ this error window! Is this cause of the parent window....
    #### This might not be so bad but the error window does not show up as a window in alt-tab, task-bar, etc.
    #### really should be visible ON TOP of main gtk window; if the main gtk window is active (and at front)
    #### Password window has same problem too....
    return _simple(gtk.MESSAGE_ERROR, short, long, parent=parent,
                   buttons=buttons, default=default)

#############

def get_dir_name():
    dialog = gtk.FileChooserDialog("Open..",
                               None,
                               gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                               (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                gtk.STOCK_OPEN, gtk.RESPONSE_OK))
    dialog.set_default_response(gtk.RESPONSE_OK)
    
    """
    filter = gtk.FileFilter()
    filter.set_name("All files")
    filter.add_pattern("*")
    dialog.add_filter(filter)

    filter = gtk.FileFilter()
    filter.set_name("Images")
    filter.add_mime_type("image/png")
    filter.add_mime_type("image/jpeg")
    filter.add_mime_type("image/gif")
    filter.add_pattern("*.png")
    filter.add_pattern("*.jpg")
    filter.add_pattern("*.gif")
    filter.add_pattern("*.tif")
    filter.add_pattern("*.xpm")
    dialog.add_filter(filter)
    """

    response = dialog.run()
    if response == gtk.RESPONSE_OK:
        result = dialog.get_filename()
    else:
        #elif response == gtk.RESPONSE_CANCEL:
        result = None
    dialog.destroy()
    return result


#############

def mypasswordprompt(prompt):
    return password('pyTombo password prompt', prompt)

class mywindow1(window1):
    #def __init__(self, create = True, accel_group = None, tooltips = None):
    def __init__(self, *args, **kwargs):
        
        language_name, self.system_encoding = locale.getdefaultlocale()
        global GLOBALsystem_encoding ## FIXME!
        GLOBALsystem_encoding = self.system_encoding ## FIXME!
        
        self.config = pytombo.config.get_config()
        self.note_dirname = self.config['notedirectory']
        #self.config.write() ## Save config
        
        ## FIXME TODO, whilstget_password_callback  is modal, can "loose" prompt window, needs to take focus and retain it!
        self.get_password_callback = pytombo.tombonote.gen_caching_get_passwordWIP(promptfunc=mypasswordprompt).gen_func()
        
        ### Need status bar at bottom  - done in glade check code gener version
        ### and divider needs to be movable. i.e need to replace hbox with HPaned -- done with glade vesion, need to check code generated version (non glade)
        ### hpan needs a decent default size for tree view
        ### icon render not working!!
        window1.__init__(self, *args, **kwargs)
        self.window1.connect('delete_event', self.gtk_main_quit)
        
        # Constant window size
        width, height = 600, 400
        slider_pos = 200
        
        # Dynamic window size based on screen size
        ## could use last used (i.e. save in preferences/config file
        screen_info = self.window1.get_screen()
        width, height = screen_info.get_width(), screen_info.get_height()
        window_size_percentage_to_screen = 50
        width, height = width / 100 * window_size_percentage_to_screen, height / 100 * window_size_percentage_to_screen
        slider_pos = width / 100 * 40

        self.window1.set_default_size(width, height)
        self.hpaned1.set_position(slider_pos)
        
        # create a TreeStore with icon and string(s)
        self.treestore = gtk.TreeStore(gtk.gdk.Pixbuf, str, str, ) ## instead of str should probably use: gobject.TYPE_STRING -- or a custome render?

        # add notes to existing tree
        add_notes_to_tree(self.treestore, self.note_dirname)

        # create the TreeView using treestore
        self.tree_notes.set_model(self.treestore)

        # create the TreeViewColumn to display the data
        self.tvcolumn = gtk.TreeViewColumn('Notes Root')

        # add tvcolumn to treeview
        self.tree_notes.append_column(self.tvcolumn)

        # create a CellRendererText to render the data
        self.cell = gtk.CellRendererText()
        self.iconRenderer = gtk.CellRendererPixbuf() ##

        # add the cell to the tvcolumn and allow it to expand
        #self.tvcolumn.pack_start(self.cell, True)
        self.tvcolumn.pack_start(self.iconRenderer, False)
        self.tvcolumn.pack_start(self.cell, True)

        # set the cell "text" attribute to column 0 - retrieve text
        # from that column in treestore
        self.tvcolumn.add_attribute(self.cell, 'text', NOTE_NAME)
        #self.tvcolumn.add_attribute(self.cell, 'text', NOTE_DIR)
        self.tvcolumn.add_attribute(self.iconRenderer, 'pixbuf', NOTE_ICON)

        # make it searchable
        self.tree_notes.set_search_column(NOTE_NAME) ## not sure about this

        # Allow sorting on the column
        self.tvcolumn.set_sort_column_id(NOTE_NAME) ## not sure about this

        # Allow drag and drop reordering of rows
        self.tree_notes.set_reorderable(True)

        self.useMarkdownSyntaxHighlighting = not False
        self.useMarkdownSyntaxHighlighting = not True ## pick up from config??
        self.note_text_buffer = gtk.TextBuffer()    
        self.on_menuitemmarkdownsyntax_activate()
        #self.text_note.set_editable(False)
        
        self.window1.set_focus(self.tree_notes)
        
        self.currentNote = AttrDict()
        self.currentNote.currentNoteFullPath = None
        #self.currentNote.currentNoteItem = None
        #self.currentNote.currentSelectedItem = self.root
        #self.currentNote.new_note_parent = None
        #self.currentNote.type = NOTE_TYPE_TEXT ## or NOTE_TYPE_ENC
        #self.currentNote.NewNote = False        
        """
        for x in self.treestore:
            print tuple(x)
        """


        

    def on_Button1_clicked(self, widget):
        print 'on_Button1_clicked'

    def on_Button2_clicked(self, widget):
        print 'on_Button2_clicked'

    def on_tree_notes_row_activated(self, widget, event, data = None):
    #def on_tree_notes_row_activated(self, treeview, row, column):
        # a built-in 'row-activated' signal
        # See http://marcin.af.gliwice.pl/listbox-part-1
        # See http://marcin.af.gliwice.pl/listbox-part-2
        # part 2 has icon render code too!
        # 
        # Also see http://www.learningpython.com/2007/02/17/pylan-a-gtd-todo-application-written-in-python-and-pygtk-part-one/
        # part 2 http://www.learningpython.com/2007/03/11/pylan-a-gtd-todo-application-written-in-python-and-pygtk-part-two/
        ###  FIXME code gen has incorrect number of args
        ## if directory toggle-expand
        """
        print 'widget', widget
        print 'event', event ## top level "things" (dir or note) get: event (x,) -- , something in dir get (x, y), something in dir, in dir get (x, y, z), ....
        print 'data', data
        print 'data', dir(data)
        selection = widget.get_selection()
        print selection
        print dir(selection)
        print selection.count_selected_rows()
        #print selection.get_data(0) ## no idea!!
        (model, iterator) = selection.get_selected()
        if iterator:
            print model.get_value(iterator, 0)
            ##print model.get_value(iterator, 1) ## not valid, can I HIDE data here? probaly do not need to but maybe folder name?
            ##Look for self.add_text_column("torrent_id", hidden=True) in List/TreeView/TreeStore
        ############
        print '-'*65
        """
        #This 2 liner is from gtk demos
        iter = self.treestore.get_iter(event)
        note_name = self.treestore.get_value(iter, NOTE_NAME)
        note_dir = self.treestore.get_value(iter, NOTE_DIR)
        icon_info = self.treestore.get_value(iter, NOTE_ICON)
        if icon_info == dirIcon:
            ## horrible directory check :-( but saves hitting filesystem/extra memory OR assuming file extensions
            if self.tree_notes.row_expanded(event):
                self.tree_notes.collapse_row(event)
            else:
                self.tree_notes.expand_row(event, False)
            ## collapse_row
        else:
            ## TODO make filename unicode (may be overhead as load_note() should too)
            note_fullpath = os.path.join(note_dir, note_name)
            self.load_note(note_fullpath) ## optional pass in encryption/plain text hint?
    


    def on_menuitemchangenotedir_activate(self, menuitem):
        temp_dir = get_dir_name()
        if temp_dir is not None:
            self.note_dirname = temp_dir
            self.config['notedirectory'] = self.note_dirname
            self.config.write()
            
            ## repopulate tree!! TODO! FIXME!
            self.treestore.clear()
            add_notes_to_tree(self.treestore, self.note_dirname)
            self.note_text_buffer.set_text('')
        

    def get_note_text(self, *args, **kwagrs):
        """returns (gui encoded) text from currently viewed/edited note
        TODO flags for all text or just highlighted text.
        TODO flag for return Unicode string type
        """
        notetext = self.note_text_buffer.get_text(self.note_text_buffer.get_start_iter(), self.note_text_buffer.get_end_iter(), include_hidden_chars=True)
        return notetext
        
    def markdown_note(self, *args, **kwagrs):
        notetext = self.get_note_text()
        notetext = notetext.decode(gui_encoding) ## is this a good idea? Probably an unneeded overhead
        
        html_str = pytombo.notedown.mymarkdown(notetext)
        ## what about https://garage.maemo.org/projects/python-gtkhtml2/ under Maemo... (or linux if available)
        #from htmltextview import HtmlTextView
        #self.text_note = HtmlTextView()
        #self.text_note.display_html(html_str) ## it doesn't like the html markdown or markdown2 (well maybe my CSS) it generates...
        ## temp markdown code
        import tempfile
        import webbrowser
        #f = tempfile.TemporaryFile()
        #f = tempfile.TemporaryFile(suffix='.html') ## can't open in web browser it whilst it is open (under Windows), close deletes it!
        #f, tempname = tempfile.mkstemp(prefix='mymarkdown', suffix='.html')
        tempname = tempfile.mktemp(prefix='mymarkdown', suffix='.html')
        f = open(tempname, 'wb')
        print 'DEBUG markdown filename:', f.name
        f.write(html_str)
        f.close()
        webbrowser.open(tempname)
        ## NOTE at this point file tempname still exists... and does NOT bet cleaned up (secrutiy risk if not was encrypted)
        
    def on_menuitemhtmlmarkdown_activate(self, *args, **kwagrs):
        #self.markdown_note()
        print 'self.useMarkdownSyntaxHighlighting', self.useMarkdownSyntaxHighlighting
        self.useMarkdownSyntaxHighlighting = not self.useMarkdownSyntaxHighlighting
        if self.useMarkdownSyntaxHighlighting:
            import pango ## got to be a better way :-(
            lang = SyntaxLoader("markdown")
            styles = { 
                    'DEFAULT':   {}, ## both 'monospace' (default) and 'serif' do not support italics (under Windows)
                    'emphasis1': {'foreground': 'dark blue', 'weight': pango.WEIGHT_BOLD, 'style': pango.STYLE_ITALIC}
                    }

        else:
            lang = None
            styles = { }
        self.note_text_buffer.reset_language(lang)
        #self.note_text_buffer.update_styles(styles)
    
    def on_menuitemmarkdownsyntax_activate(self, *args, **kwagrs):
        print 'self.useMarkdownSyntaxHighlighting', self.useMarkdownSyntaxHighlighting
        self.useMarkdownSyntaxHighlighting = not self.useMarkdownSyntaxHighlighting
        modified = self.note_text_buffer.get_modified()
        notetextcopy = self.get_note_text()
        if self.useMarkdownSyntaxHighlighting:
            import pango ## got to be a better way :-(
            '''
            styles = { 
                    'DEFAULT':   {}, ## both 'monospace' (default) and 'serif' do not support italics (under Windows)
                    'bold': {'weight': 700}, # emhasis 1,2,...
                    'emphasis1': {'foreground': 'sea green', 'weight': pango.WEIGHT_BOLD, 'style': pango.STYLE_ITALIC}, ## pango.STYLE_OBLIQUE}, ## pango.STYLE_ITALIC
                    'emphasis2': {'foreground': 'sea green', 'weight': pango.WEIGHT_BOLD},
                    'heading': {'weight': pango.WEIGHT_HEAVY, 'foreground': 'dark blue', 'underline': pango.UNDERLINE_DOUBLE, 'scale': pango.SCALE_X_LARGE}, ##pango.SCALE_LARGE}, ##{'underline': pango.UNDERLINE_DOUBLE},
                    'bullet_list': {'foreground': 'blue'},
                    'numbered_list': {'foreground': 'blue'},
                    'code' : {'foreground': 'blue'},
                    'quoted_text' : {'foreground': 'blue'},
                    'url' : {'foreground': 'magenta', 'underline': pango.UNDERLINE_SINGLE},
                    }
            '''
            styles = { 
                    'DEFAULT':   {}, ## both 'monospace' (default) and 'serif' do not support italics (under Windows)
                    'emphasis1': {'foreground': 'dark blue', 'weight': pango.WEIGHT_BOLD, 'style': pango.STYLE_ITALIC}
                    }
            lang = SyntaxLoader("markdown")
            #lang = None
            self.note_text_buffer = CodeBuffer(lang=lang, styles=styles)
        else:
            self.note_text_buffer = gtk.TextBuffer()
        self.note_text_buffer.set_text(notetextcopy)
        self.text_note.set_buffer(self.note_text_buffer)
        self.note_text_buffer.set_modified(modified)

    def find_in_current_note_text(self, *args, **kwagrs):
        ## initially start at top of note
        search_term = u'List' ## case sensitive??
        search_term = u'list' ## case sensitive??
        #start_position = self.note_text_buffer.get_start_iter()
        #match_start, match_end = iter.forward_search(search_term, gtk.TEXT_SEARCH_VISIBLE_ONLY, limit=None)
        #print 'match_start, match_end', match_start, match_end 
        
        highlightText(self.note_text_buffer, search_term)


    def on_menuitemoptions_activate(self, *args, **kwagrs):
        ## TODO Fix up, length of columns (name) too long, and data is truncated/not visible
        x = create_gtk_dialog_from_dict(self.config)
        result = x.run()
        if result == gtk.RESPONSE_OK: ## -5:
            self.config.write() ## Save config
        """
        elif result == gtk.RESPONSE_CANCEL: ##-6:
            print 'config dialog window Cancelled'
        elif result == -4:  ## need constant lookup gtk.RESPONSE_CLOSE:
            print 'closed config dialog window'
        else:
            print 'config dialog window no idea what happened'
        """
    
    def on_menuSave_activate(self, *args, **kwagrs):
        print 'on_menuSave_activate'
        if not self.note_text_buffer.get_modified():
            ## do not bother saving...
            print 'not saving'
            return
        print 'saving'
        #print args
        #print kwagrs
        notetext = self.get_note_text()
        notetext = notetext.decode(gui_encoding) ## is this a good idea? Probably an unneeded overhead BUT we need it for filename gen
        ## a good Unicode character to use for testing in latin1 is u'\N{GREEK CAPITAL LETTER SIGMA}'
        try:
            file_bytes = notetext.encode(self.config['file_encoding']) ## if self.config['file_encoding'] is utf8 we should not need this (overhead)
        except UnicodeEncodeError, info:
            print UnicodeEncodeError, info
            if hasattr(info, 'end'):
                print info.end
            first_bad_character = notetext[info.start]
            print urepr(first_bad_character)
            print repr(first_bad_character)
            highlightText(self.note_text_buffer, first_bad_character)
            ## WARNING
            dummy_result = error('Bad characters in note. Save aborted.', 'Character(s) were found in the note that can not be saved in the file encoding "%s". Correct the highlighted character "%s" in the note (or choose a different file encoding).' % (self.config['file_encoding'], first_bad_character))
            return
            
        #self.note_text_buffer.set_modified(False)
        
        # generate new name in case it changed...
        derived_name = pytombo.tombonote.default_note_name(notetext)
        if self.currentNote.currentNoteFullPath is None:
            # New note
            ## Get path from tree and use that
            extension_str = '.txt' ## '.chi'
            
            tselected = self.tree_notes.get_selection()
            print tselected
            treestore, treeinter = tselected.get_selected()
            print treeinter 
            #iter = self.treestore.get_iter()#event)
            iter = treeinter
            if iter is None:
                the_path = self.config['notedirectory']
            else:
                #note_name = self.treestore.get_value(iter, NOTE_NAME)
                #note_dir = self.treestore.get_value(iter, NOTE_DIR)
                #icon_info = self.treestore.get_value(iter, NOTE_ICON)
                note_name = treestore.get_value(iter, NOTE_NAME)
                note_dir = treestore.get_value(iter, NOTE_DIR)
                icon_info = treestore.get_value(iter, NOTE_ICON)
                print note_name 
                print note_dir
                the_path = os.path.join(note_dir, note_name)
                if not os.path.isdir(the_path):
                    the_path = os.path.dirname(the_path)

            filename = os.path.join(the_path, derived_name)
        else:
            extension_str = os.path.splitext(self.currentNote.currentNoteFullPath)[1]
            filename = os.path.join(os.path.dirname(self.currentNote.currentNoteFullPath), derived_name)
        filename = filename + extension_str
        print 'filename ', filename 
        if not pytombo.search.is_encrypted(filename):
            save_text_note(filename, file_bytes, original_filename=self.currentNote.currentNoteFullPath, backup_extn=self.config['backup_file_extension'])
            self.currentNote.currentNoteFullPath = filename
            self.note_text_buffer.set_modified(False)
        else:
            raise encrypted_save_not_implemented
        ## TODO! Add note to tree!
        
    def get_selected_tree_note_path(self):
        """Returns absolute path of highlighted file or directory in tree
        """
        tselected = self.tree_notes.get_selection()
        treestore, treeinter = tselected.get_selected()
        if treeinter is None:
            # nothing selected
            note_name = None
        else:
            note_name = treestore.get_value(treeinter, NOTE_NAME)
            note_dir = treestore.get_value(treeinter, NOTE_DIR)
            icon_info = treestore.get_value(treeinter, NOTE_ICON)
            note_name = os.path.join(note_dir, note_name)
            note_name = note_name.decode(gui_encoding)
        
        return note_name, treeinter
        
    def on_menuitemencrypt_activate(self, *args, **kwagrs):
        plaintext_path, treeinter = self.get_selected_tree_note_path()
        
        ## Most of the tests below are sanity tests for debugging,
        ## the main check at the end makes these redundant.
        ## Remove at some point or place in "if debug" check
        if plaintext_path is None:
            # nothing selected
            error("can't encrypt NOTHING", parent=self.window1)
            return
        if os.path.isdir(plaintext_path):
            error('can not encrypt directories')
            return
        if pytombo.search.is_encrypted(plaintext_path):
            error('can not encrypt already encrypted files')
            return
        if pytombo.search.is_text(plaintext_path) and not os.path.isdir(plaintext_path) and os.path.exists(plaintext_path):
            ## really paranoid check!
            base_filename = os.path.splitext(plaintext_path)[0]
            encrypted_path = base_filename + pytombo.tombonote.ENCRYPTED_EXTENSION
            notename = pytombo.tombonote.remove_leading_path(self.config['notedirectory'], encrypted_path)
            if os.path.exists(encrypted_path):
                error('There is already an encrypted file called %s' % notename)
                return
            current_password = self.get_password_callback(prompt=False)
            if current_password is None:
                current_password = password('pyTombo password prompt', 'Enter the password that will encrypt note %s' % notename, confirm=True)
            if current_password is None:
                return
            self.get_password_callback(reset=True, value=current_password)
            current_password = self.get_password_callback(prompt=False)
            note_text = pytombo.tombonote.file_contents(plaintext_path, None)

            backup_name = mybackup(plaintext_path, backup_extn=self.config['backup_file_extension'])
            pytombo.tombonote.encrypt_file(encrypted_path, note_text, current_password) ## TODO! , dos_newlines= config option!!
            self.treestore.set_value(treeinter, NOTE_ICON, encryptedIcon)
            self.treestore.set_value(treeinter, NOTE_NAME, os.path.basename(encrypted_path))
            ## if not backup; os.remove  (backup_name ) ## TODO! no backup support

    def on_genericFunc_activate(self, *args, **kwagrs):
        print 'on_genericFunc_activate'
        print args
        print kwagrs

    def gtk_main_quit(self, *args, **kwagrs):
        ## def reallyClose(self, widget, event, data = None):
        # http://marcin.af.gliwice.pl/if-then-else-20070604155058
        really_close = True
        #really_close = False
        if really_close:
            gtk.main_quit()
            return False ## redundant...
        else:
            print "The window will never be closed in this way"
            return True

    def load_note(self, note_fullpath):
        ## move into init.... and settings
        self.text_note.set_wrap_mode(gtk.WRAP_WORD) ## gtk.WRAP_NONE, gtk.WRAP_CHAR, gtk.WRAP_WORD or gtk.WRAP_WORD_CHAR
        ### add line wrap (as option) to TextView
        
        if not isinstance(note_fullpath, unicode):
            # probably in local encoding. We should convert to Unicode, in case there is a non-ASCII character. 
            # e.g. latin1 under Windows, utf8 for most modern Linux distros
            note_fullpath = note_fullpath.decode(gui_encoding)
        
        try:
            note_text = pytombo.tombonote.file_contents(note_fullpath, self.get_password_callback)
            # probaly want to text encoding in case of errors, i.e. do not want to loose data due to exceptions
            #encoder, decoder, streamreader, streamwriter = codecs.lookup(self.config['file_encoding'])
            #if pytombo.search.is_encrypted(note_fullpath):
                
            ##self.text_note.set_editable(True) ## TODO! Implement once happy
        except pytombo.chi_io.BadPassword:
            note_text = 'bad or missing password'
            self.text_note.set_editable(False)
            self.note_text_buffer.set_text(note_text) ## UTF8 or Unicode.... FIXME!
            return
        note_text = note_text.decode(self.config['file_encoding']) ## if self.config['file_encoding'] is utf8 we should not need this (overhead)
        self.note_text_buffer.set_text(note_text) ## UTF8 or Unicode.... FIXME!
        self.note_text_buffer.set_modified(False)
        
        self.currentNote.currentNoteFullPath = note_fullpath
        ##if pytombo.search.is_encrypted(note_name):
        
        
        """
        x = self.note_text_buffer.get_text(self.note_text_buffer.get_start_iter(), self.note_text_buffer.get_end_iter(), include_hidden_chars=True)
        ## we probbaly got back utf8 irrespective of data content/system encoding.
        print type(x)
        print repr(x)
        print type(x)
        print type(unicode(x))
        """
        


def main():
    main_window = mywindow1()
    main_window.window1.show()
    gtk.main()

if __name__ == "__main__":
    main()
    
